#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version        : 1.0
# @Create Time    : 2023/6/21 10:10 
# @File           : scheduler.py
# @IDE            : PyCharm
# @desc           : 简要说明

import datetime
from typing import List, Optional, Union
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
from apscheduler.triggers.date import DateTrigger
from apscheduler.triggers.interval import IntervalTrigger
from apscheduler.job import Job


class Scheduler:
    def __init__(self):
        self.scheduler = None
        self.db = None

    def start(self) -> None:
        """
        创建调度器

        :return:
        """
        self.scheduler = AsyncIOScheduler()
        self.scheduler.start()

    def add_job(
            self,
            job_func,
            trigger: Union[CronTrigger, DateTrigger, IntervalTrigger],
            name: str = None,
            *args,
            **kwargs
    ) -> Optional[Job]:
        """
        date触发器用于在指定的日期和时间触发一次任务。它适用于需要在特定时间点执行一次的任务，例如执行一次备份操作。

        :param job_func: 任务函数
        :param trigger: 触发条件
        :param name: 任务名称
        :return:
        """
        if job_func:
            return self.scheduler.add_job(job_func, trigger=trigger, args=args, kwargs=kwargs, id=name)
        else:
            raise ValueError(f"添加任务失败，未找到该方法：{job_func}")

    def add_cron_job(
            self,
            job_func,
            expression: str,
            start_date: str = None,
            end_date: str = None,
            timezone: str = "Asia/Shanghai",
            name: str = None,
            args: tuple = (),
            **kwargs
    ) -> Optional[Job]:
        """
        通过 cron 表达式添加定时任务

        :param job_func: 任务函数
        :param expression: cron 表达式，六位或七位，分别表示秒、分钟、小时、天、月、星期几、年
        :param start_date: 触发器的开始日期时间。可选参数，默认为 None。
        :param end_date: 触发器的结束日期时间。可选参数，默认为 None。
        :param timezone: 时区，表示触发器应用的时区。可选参数，默认为 None，使用上海默认时区。
        :param name: 任务名称
        :param args: 非关键字参数
        :return:
        """
        second, minute, hour, day, month, day_of_week, year = self.__parse_cron_expression(expression)
        trigger = CronTrigger(
            second=second,
            minute=minute,
            hour=hour,
            day=day,
            month=month,
            day_of_week=day_of_week,
            year=year,
            start_date=start_date,
            end_date=end_date,
            timezone=timezone
        )
        return self.add_job(job_func, trigger, name, *args, **kwargs)

    def add_interval_job(
            self,
            job_func,
            expression: str,
            start_date: Union[str, datetime.datetime] = None,
            end_date: Union[str, datetime.datetime] = None,
            timezone: str = "Asia/Shanghai",
            jitter: int = None,
            name: str = None,
            args: tuple = (),
            **kwargs
    ) -> Optional[Job]:
        """
        date触发器用于在指定的日期和时间触发一次任务。它适用于需要在特定时间点执行一次的任务，例如执行一次备份操作。

        :param job_func: 任务函数
        :param expression：interval 表达式，分别为：秒、分、时、天、周，例如，设置 10 * * * * 表示每隔 10 秒执行一次任务。
        :param end_date: 表示任务的结束时间，可以设置为 datetime 对象或者字符串。
                         例如，设置 end_date='2023-06-23 10:00:00' 表示任务在 2023 年 6 月 23 日 10 点结束。
        :param start_date: 表示任务的起始时间，可以设置为 datetime 对象或者字符串。
                           例如，设置 start_date='2023-06-22 10:00:00' 表示从 2023 年 6 月 22 日 10 点开始执行任务。
        :param timezone：表示时区，可以设置为字符串或 pytz.timezone 对象。例如，设置 timezone='Asia/Shanghai' 表示使用上海时区。
        :param jitter：表示时间抖动，可以设置为整数或浮点数。例如，设置 jitter=2 表示任务的执行时间会在原定时间上随机增加 0~2 秒的时间抖动。
        :param name: 任务名称
        :param args: 非关键字参数
        :return:
        """
        second, minute, hour, day, week = self.__parse_interval_expression(expression)
        trigger = IntervalTrigger(
            weeks=week,
            days=day,
            hours=hour,
            minutes=minute,
            seconds=second,
            start_date=start_date,
            end_date=end_date,
            timezone=timezone,
            jitter=jitter
        )
        return self.add_job(job_func, trigger, name, *args, **kwargs)

    @staticmethod
    def __parse_cron_expression(expression: str) -> tuple:
        """
        解析 cron 表达式

        :param expression: cron 表达式，支持六位或七位，分别表示秒、分钟、小时、天、月、星期几、年
        :return: 解析后的秒、分钟、小时、天、月、星期几、年字段的元组
        """
        fields = expression.strip().split()

        if len(fields) not in (6, 7):
            raise ValueError("无效的 Cron 表达式")

        parsed_fields = [None if field in ('*', '?') else field for field in fields]
        if len(fields) == 6:
            parsed_fields.append(None)

        return tuple(parsed_fields)

    @staticmethod
    def __parse_interval_expression(expression: str) -> tuple:
        """
        解析 interval 表达式

        :param expression: interval 表达式，分别为：秒、分、时、天、周，例如，设置 10 * * * * 表示每隔 10 秒执行一次任务。
        :return:
        """
        # 将传入的 interval 表达式拆分为不同的字段
        fields = expression.strip().split()

        if len(fields) != 5:
            raise ValueError("无效的 interval 表达式")

        parsed_fields = [int(field) if field != '*' else 0 for field in fields]
        return tuple(parsed_fields)

    def shutdown(self) -> None:
        """
        关闭调度器

        :return:
        """
        self.scheduler.shutdown()
        